/* $Id: connection.c,v 1.3 2006/03/15 03:49:16 jfb Exp $ */
/*
 * Copyright (c) 2003 Jean-Francois Brousseau <phrakt@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
 * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/param.h>
#include <sys/socket.h>

#include <netinet/in.h>
#include <arpa/inet.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <canopy/log.h>
#include <canopy/mem.h>
#include <canopy/string.h>

#include "msg.h"
#include "conn.h"
#include "http.h"
#include "entity.h"
#include "private.h"




static int   http_conn_growrbuf (http_conn_t *, size_t);
static int   http_conn_sendbuf  (http_conn_t *, size_t);
static int   http_conn_sendent  (http_conn_t *, http_ent_t *);


/*
 * http_conn_alloc()
 *
 * Allocate a new HTTP connection structure and return it.
 */
http_conn_t*
http_conn_alloc(int sock)
{
	http_conn_t *conn;

	if ((conn = cnp_malloc(sizeof(*conn))) == NULL) {
		http_log(CNP_LOG_ERRNO, "failed to allocate HTTP connection");
		return (NULL);
	}

	memset(conn, 0, sizeof(*conn));

	conn->hc_sock = sock;
	conn->hc_refcnt = 1;
	conn->hc_state = HTTP_CONN_OPEN;

	conn->hc_ralen = sizeof(conn->hc_raddr);
	conn->hc_lalen = sizeof(conn->hc_laddr);

	conn->hc_rbuflen = HTTP_CONN_INITBUFSIZE;
	conn->hc_wbuflen = HTTP_CONN_INITBUFSIZE;

	if ((conn->hc_rbuf = cnp_malloc(conn->hc_rbuflen)) == NULL) {
		http_log(CNP_LOG_ERRNO, "failed to allocate connection "
		    "read buffer");
		http_conn_free(conn);
		return (NULL);
	}

	if ((conn->hc_wbuf = cnp_malloc(conn->hc_wbuflen)) == NULL) {
		http_log(CNP_LOG_ERRNO, "failed to allocate connection "
		    "write buffer");
		http_conn_free(conn);
		return (NULL);
	}

	if (pthread_mutex_init(&conn->hc_lock, NULL) != 0) {
		http_conn_free(conn);
		return (NULL);
	}

	return (conn);
}

/*
 * http_conn_close()
 *
 */
int
http_conn_close(http_conn_t *conn)
{
	int ret;

	if ((ret = pthread_mutex_lock(&conn->hc_lock)) != 0) {
		http_log(CNP_LOG_ERR, "failed to lock connection");
		return (-1);
	}

	if (conn->hc_sock >= 0) {
		close(conn->hc_sock);
		conn->hc_sock = -1;
		conn->hc_state = HTTP_CONN_CLOSED;
	}
	else
		ret = -1;

	(void)pthread_mutex_unlock(&conn->hc_lock);

	return (0);
}

/*
 * http_conn_getref()
 *
 * Returns a reference to the connection <conn>.
 */
http_conn_t*
http_conn_getref(http_conn_t *conn)
{
	if (pthread_mutex_lock(&conn->hc_lock) != 0) {
		http_log(CNP_LOG_ERR, "failed to lock connection");
		return (NULL);
	}

	conn->hc_refcnt++;

	(void)pthread_mutex_unlock(&conn->hc_lock);

	return (conn);
}

/*
 * http_conn_free()
 *
 * Free an HTTP connection <conn> and all of its associated resources.
 */
void
http_conn_free(http_conn_t *conn)
{
	if (pthread_mutex_lock(&conn->hc_lock) != 0) {
		http_log(CNP_LOG_ERR, "failed to lock connection");
	}

	conn->hc_refcnt--;

	(void)pthread_mutex_unlock(&conn->hc_lock);

	if (conn->hc_refcnt == 0) {
		if (conn->hc_sock >= 0)
			(void)close(conn->hc_sock);

		if (conn->hc_rbuf != NULL)
			cnp_free(conn->hc_rbuf);

		if (conn->hc_wbuf != NULL)
			cnp_free(conn->hc_wbuf);

		pthread_mutex_destroy(&conn->hc_lock);
		cnp_free(conn);
	}
}

/*
 * http_conn_setflag()
 *
 */
int
http_conn_setflag(http_conn_t *conn, int flag)
{
	if (pthread_mutex_lock(&conn->hc_lock) != 0) {
		http_log(CNP_LOG_ERR, "failed to lock connection");
		return (-1);
	}

	conn->hc_flags |= flag;

	if (pthread_mutex_unlock(&conn->hc_lock) != 0)
		http_log(CNP_LOG_ERR, "failed to unlock connection");

	return (0);
}

/*
 * http_conn_getflag()
 *
 */
int
http_conn_getflag(http_conn_t *conn, int flag)
{
	return (conn->hc_flags & flag);
}

/*
 * http_conn_getfd()
 *
 * Returns the file descriptor associated with the connection <conn>.
 */
int
http_conn_getfd(http_conn_t *conn)
{
	return (conn->hc_sock);
}

/*
 * http_conn_getstate()
 *
 * Returns the connection <conn>'s state.
 */
enum http_conn_state
http_conn_getstate(http_conn_t *conn)
{
	return (conn->hc_state);
}

/*
 * http_conn_recvreq()
 *
 * Receive an HTTP request on the connection <conn> and use the buffer pointed
 * to by <rbuf> to store the raw data (or an internal buffer if <rbuf> is NULL).
 * If the buffer is too small, it will be grown to accomodate for the entire
 * request unless the HTTP_REQ_STATBUF flag is specified, in which case the
 * call will fail.
 * Returns the HTTP request on success, or NULL on failure.
 */
http_msg_t*
http_conn_recvreq(http_conn_t *conn, int flags)
{
	int done;
	ssize_t ret;
	size_t i, start, end, total, left;
	uint8_t *buf, *bp;
	http_msg_t *req;

	if (pthread_mutex_lock(&conn->hc_lock) != 0) {
		http_log(CNP_LOG_ERR, "failed to lock connection");
		return (NULL);
	}

	bp = buf = (uint8_t *)conn->hc_rbuf;
	left = conn->hc_rbuflen;
	total = start = 0;
	done = 0;

	http_log(CNP_LOG_DEBUG, "receiving request from ? on fd %d",
	    conn->hc_sock);

	do {
		if ((ret = recv(conn->hc_sock, bp, left, 0)) == 0) {
			http_log(CNP_LOG_INFO, "connection closed");
			conn->hc_state = HTTP_CONN_CLOSED;

			if (total == 0) {
				(void)pthread_mutex_unlock(&conn->hc_lock);
				return (NULL);
			}

			break;
		} else if (ret == -1) {
			if (errno == EAGAIN)
				continue;
			http_log(CNP_LOG_ERRNO, "receive error");
			(void)pthread_mutex_unlock(&conn->hc_lock);
			return (NULL);
		}

		total += (size_t)ret;
		http_log(CNP_LOG_DEBUG, "%d bytes received (total=%u)",
		    (int)ret, (unsigned int)total);

		/*
		 * Look for the 2 consecutive CRLFs that mark the end of the
		 * request headers.  If we find only one, this is the end
		 * of a single header, and we update <start> to start from
		 * there on the next lookup...
		 */
		end = total - start - 3;
		for (i = start; i < end; i++) {
			if ((buf[i] == '\r') && (buf[i + 1] == '\n')) {
				if ((buf[i + 2] == '\r') &&
				    (buf[i + 3] == '\n')) {
					/* end of headers found */
					done = 1;
					break;
				}
				else
					start = i + 2;
			}

		}

		bp += (size_t)ret;
		left -= (size_t)ret;

		if (left == 0) {
			http_conn_growrbuf(conn, conn->hc_rbuflen + 1024);
			bp = ((uint8_t *)conn->hc_rbuf) + total;
			left = 1024;
		}
	} while (!done);

	*bp = '\0';

	req = http_msg_parse(conn->hc_rbuf, total);

	if (req != NULL) {
		req->msg_req.req_conn = conn;
		conn->hc_refcnt++;
	}

	if (pthread_mutex_unlock(&conn->hc_lock) != 0)
		http_log(CNP_LOG_ERR, "failed to unlock connection");

	return (req);
}

/*
 * http_conn_sendresp()
 *
 * Send the HTTP response message <resp> to the other endpoint of the
 * connection <conn>.  The argument <flags> controls which portions of the
 * message are sent and how.
 * Returns 0 on success, or -1 on failure.
 */
int
http_conn_sendresp(http_conn_t *conn, http_msg_t *resp, int flags)
{
	ssize_t ret;

	if (pthread_mutex_lock(&conn->hc_lock) != 0) {
		http_log(CNP_LOG_ERR, "failed to lock connection");
		return (-1);
	}

	ret = http_msg_format(resp, &conn->hc_wbuf, &conn->hc_wbuflen,
	    HTTP_MSG_HDR | HTTP_MSG_DYNBUF);
	if (ret == -1) {
		(void)pthread_mutex_unlock(&conn->hc_lock);
		return (-1);
	}

	http_log(CNP_LOG_DEBUG, "sending %d bytes of HTTP response", ret);

	if (http_conn_sendbuf(conn, (size_t)ret) == -1) {
		(void)pthread_mutex_unlock(&conn->hc_lock);
		return (-1);
	}

	if (pthread_mutex_unlock(&conn->hc_lock) != 0)
		http_log(CNP_LOG_ERR, "failed to unlock connection");


	if (flags & HTTP_MSG_BODY) {
		http_conn_sendent(conn, resp->msg_entity);
	}

	return (0);
}

/*
 * http_conn_sendfd()
 *
 * Send the first <len> bytes of arbitrary data from the source buffer
 * <src> over the HTTP connection <conn>.
 * Returns 0 on success, or -1 on failure.
 */
ssize_t
http_conn_sendfd(http_conn_t *conn, int fd, size_t len, int flags)
{
	ssize_t ret, total = 0;
	size_t left = len;

	for (left = len; left > 0; left -= (size_t)ret) {
		ret = read(fd, conn->hc_wbuf, MIN(left, conn->hc_wbuflen));
		if (ret == -1) {
			if (errno == EINTR)
				continue;

			http_log(CNP_LOG_ERRNO, "failed to read from fd %d", fd);
			break;
		}
		else if (ret == 0)
			break;

		http_conn_send(conn, conn->hc_wbuf, (size_t)ret, flags);

		total += ret;
	}

	return (total);
}

/*
 * http_conn_send()
 *
 * Send the first <len> bytes of arbitrary data from the source buffer
 * <src> over the HTTP connection <conn>.
 * Returns 0 on success, or -1 on failure.
 */
ssize_t
http_conn_send(http_conn_t *conn, const void *src, size_t len, int flags)
{
	int fl = 0;
	ssize_t ret;
	size_t left;
	const uint8_t *bp;

#ifdef MSG_NOSIGNAL
	fl |= MSG_NOSIGNAL;
#endif

	left = len;
	bp = (const uint8_t *)src;

	if (pthread_mutex_lock(&conn->hc_lock) != 0) {
		http_log(CNP_LOG_ERR, "failed to lock connection");
		return (-1);
	}

	do {
		ret = send(conn->hc_sock, bp, left, fl);
		if (ret == -1) {
			if (errno == EINTR)
				continue;

			if ((errno == EPIPE) || (errno == ECONNRESET)) {
				/* connection closed remotely */
				conn->hc_state = HTTP_CONN_CLOSED;
				break;
			}

			http_log(CNP_LOG_ERRNO, "send error");
		}

		bp += (size_t)ret;
		left -= (size_t)left;

	} while (left > 0);

	if (pthread_mutex_unlock(&conn->hc_lock) != 0)
		http_log(CNP_LOG_ERR, "failed to unlock connection");

	return (ssize_t)(len - left);
}

/*
 * http_conn_growrbuf()
 *
 */
static int
http_conn_growrbuf(http_conn_t *conn, size_t newsize)
{
	void *tmp;

	if ((tmp = cnp_realloc(conn->hc_rbuf, newsize)) == NULL) {
		http_log(CNP_LOG_ERRNO, "failed to grow connection read buffer");
		return (-1);
	}

	conn->hc_rbuf = tmp;
	conn->hc_rbuflen = newsize;
	return (0);
}

/*
 * http_conn_sendbuf()
 *
 */
static int
http_conn_sendbuf(http_conn_t *conn, size_t len)
{
	int flags = 0;
	ssize_t ret;
	size_t total, left;
	uint8_t *buf, *bp;

	total = left = len;
	buf = bp = (uint8_t *)conn->hc_wbuf;

#ifdef MSG_NOSIGNAL
	flags |= MSG_NOSIGNAL;
#endif

	do {
		ret = send(conn->hc_sock, bp, left, flags);
		if (ret == -1) {
			if (errno == EINTR)
				continue;

			if ((errno == EPIPE) || (errno == ECONNRESET)) {
				/* connection closed remotely */
				conn->hc_state = HTTP_CONN_CLOSED;
				break;
			}

			http_log(CNP_LOG_ERRNO, "response send error");
		}

		bp += (size_t)ret;
		left -= (size_t)left;

	} while (left > 0);

	return (0);
}

/*
 * http_conn_sendent()
 *
 * Send the HTTP entity <entity> over the connection <conn>.
 * Returns 0 on success, or -1 on failure.
 */
static int
http_conn_sendent(http_conn_t *conn, http_ent_t *entity)
{
	ssize_t ret;
	off_t off = 0;


	for (;;) {
		ret = http_entity_read(entity, off, conn->hc_wbuf,
		    conn->hc_wbuflen);
		if (ret == -1) {
			http_log(CNP_LOG_ERR, "failed to read entity");
			return (-1);
		}
		if (ret == 0)
			break;

		off += (off_t)ret;

		ret = http_conn_send(conn, conn->hc_wbuf, (size_t)ret, 0);
	}

	return (0);
}
